/*
 * Copyright 2007-2008 Andrew O'Malley
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package ws.quokka.plugin.remotecopy.webdav;

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpURL;
import org.apache.commons.httpclient.HttpsURL;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.protocol.Protocol;

import org.apache.tools.ant.BuildException;

import ws.quokka.core.bootstrap_util.Assert;
import ws.quokka.core.bootstrap_util.Logger;
import ws.quokka.core.plugin_spi.support.TypedProperties;

import ws.quokka.plugin.remotecopy.RemoteCopy;
import ws.quokka.plugin.remotecopy.RemoteCopyPlugin;
import ws.quokka.plugin.remotecopy.http.EasySSLProtocolSocketFactory;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


/**
 * Copies
 */
public class WebDavRemoteCopy implements RemoteCopy {
    //~ Static fields/initializers -------------------------------------------------------------------------------------

    private static final int NOT_FOUND = 404;
    private static final int OK = 200;
    private static final int CREATED = 201;

    //~ Instance fields ------------------------------------------------------------------------------------------------

    private HttpClient client;
    private HttpURL url;
    private Logger log;

    //~ Constructors ---------------------------------------------------------------------------------------------------

    public WebDavRemoteCopy(TypedProperties properties, Logger log) {
        this.log = log;

        try {
            if (log.isDebugEnabled()) {
                // Prints out the HTTP headers
                System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog");
                System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true");
                System.setProperty("org.apache.commons.logging.simplelog.log.httpclient.wire.header", "debug");
                System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.commons.httpclient", "debug");
            }

            String urlString = properties.getString("url");
            url = urlString.startsWith("https") ? new HttpsURL(urlString) : new HttpURL(urlString);

            boolean acceptInvalidCertificates = properties.getBoolean("acceptInvalidCertificates", false);

            if (acceptInvalidCertificates) {
                Protocol.unregisterProtocol("https");
                Protocol.registerProtocol("https",
                    new Protocol("https", new EasySSLProtocolSocketFactory(), url.getPort()));
            }

            client = new HttpClient();

            HostConfiguration hostConfiguration = new HostConfiguration();
            hostConfiguration.setHost(url.getHost(), url.getPort(), Protocol.getProtocol(url.getScheme()));
            client.setHostConfiguration(hostConfiguration);

            String user = properties.getString("user", null);
            String password = properties.getString("password", null);

            if ((user != null) && (password != null)) {
                url.setUserinfo(user, password);

                Credentials credentials = new UsernamePasswordCredentials(user, password);
                client.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), AuthScope.ANY_REALM),
                    credentials);
                client.getParams().setAuthenticationPreemptive(true);
            }
        } catch (URIException e) {
            throw new BuildException(e);
        }
    }

    //~ Methods --------------------------------------------------------------------------------------------------------

    public void copy(File file, String path) {
        try {
            path = path.startsWith("/") ? path.substring(1) : path;
            url.setPath(RemoteCopyPlugin.normalise(url.getPath()));

            String uri = url.toString() + path;
            log.info("Copying " + file.getAbsolutePath() + " to " + uri);
            path = url.getPath() + path;

            String[] dirs = path.split("/");
            String createDir = "/";

            for (int i = 1; i < (dirs.length - 1); i++) {
                String dir = dirs[i];
                createDir += (dir + "/");

                // Check it exists, if not create
                // Checking is slow, but safer as the server can be configured to block MKCOL for certain users
                HeadMethod head = new HeadMethod(createDir);
                int response = client.executeMethod(head);
                Assert.isTrue((response == OK) || (response == NOT_FOUND),
                    "Invalid response from HEAD for '" + createDir + "': " + response);

                if (response == NOT_FOUND) {
                    MkColMethod mkCol = new MkColMethod(createDir);
                    response = client.executeMethod(mkCol);
                    Assert.isTrue((response == OK) || (response == CREATED),
                        "Invalid response creating dir '" + createDir + "': " + response);
                }
            }

            PutMethod put = new PutMethod(uri);
            put.setRequestBody(new BufferedInputStream(new FileInputStream(file)));

            int response = client.executeMethod(put);
            Assert.isTrue((response == OK) || (response == CREATED),
                "Invalid response putting '" + uri + "': " + response);
        } catch (IOException e) {
            throw new BuildException(e);
        }
    }

    public void close() {
        client.getHttpConnectionManager().closeIdleConnections(0);
    }
}
